package jflowmap.model.ontology;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Vector;

import jflowmap.data.ValueReaders;
import jflowmap.data.ViewConfig;
import jflowmap.model.ontology.calculation.Calculation;
import jflowmap.model.ontology.calculation.CalculationDefinition;
import jflowmap.model.ontology.calculation.Constraint;
import jflowmap.model.ontology.calculation.DynamicDimensionDefinition;
import jflowmap.model.ontology.calculation.ResultDefinition;
import jflowmap.model.ontology.calculation.UnboundConstraint;
import jflowmap.model.ontology.calculation.VariableDefinition;
import jflowmap.model.ontology.calculation.expression.Addition;
import jflowmap.model.ontology.calculation.expression.Constant;
import jflowmap.model.ontology.calculation.expression.Division;
import jflowmap.model.ontology.calculation.expression.Expression;
import jflowmap.model.ontology.calculation.expression.Multiplication;
import jflowmap.model.ontology.calculation.expression.Substraction;
import jflowmap.model.ontology.calculation.expression.VariableExpression;
import jflowmap.util.Pair;

import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

import fri.patterns.interpreter.parsergenerator.Lexer;
import fri.patterns.interpreter.parsergenerator.Parser;
import fri.patterns.interpreter.parsergenerator.ParserTables;
import fri.patterns.interpreter.parsergenerator.Token;
import fri.patterns.interpreter.parsergenerator.lexer.LexerBuilder;
import fri.patterns.interpreter.parsergenerator.parsertables.LALRParserTables;
import fri.patterns.interpreter.parsergenerator.semantics.ReflectSemantic;
import fri.patterns.interpreter.parsergenerator.syntax.Syntax;
import fri.patterns.interpreter.parsergenerator.syntax.builder.SyntaxSeparation;

public class ContextBuilder extends ReflectSemantic {

	private static String rules[][] = {
		{ "ONTOLOGY", "SENTENCES" },
		
		
		{ "SENTENCES", "SENTENCE" },
		{ "SENTENCES", "SENTENCES", "SENTENCE" },
		{ "SENTENCE", "NAMESPACE", "'.'" },
		{ "SENTENCE", "DEFINITION", "'.'" },
		{ "SENTENCE", "INCLUDE_DIRECTIVE", "'.'" },
		{ "DEFINITION", "ATTRIBUTE_DECLARATION" },
		{ "DEFINITION", "DIMENSION_DEFINITION" },
		{ "DEFINITION", "OTHER_DEFINITION" },
		
		{ "INCLUDE_DIRECTIVE", "'Include'", "IDENTIFIER_LIST" },
		{ "NAMESPACE", "'Context'", "IDENTIFIER" },

		{ "DIMENSION_DEFINITION", "DIMENSION_LIST", "'is-a'", "IDENTIFIER" },
		{ "DIMENSION_DEFINITION", "DIMENSION_LIST", "'part-of'", "IDENTIFIER" },
		{ "DIMENSION_LIST", "IDENTIFIER" },
		{ "DIMENSION_LIST", "DIMENSION_LIST", "','", "IDENTIFIER" },
		{ "DIMENSION_LIST", "'Ordered'", "'('", "DIMENSION_LIST", "')'" },

		{ "OTHER_DEFINITION", "AGGREGATE_DEFINITION" },
		{ "OTHER_DEFINITION", "MEASURE_DEFINITION" },
		{ "OTHER_DEFINITION", "VALUE_DEFINITION" },
		{ "OTHER_DEFINITION", "SYNONYME_DEFINITION" },
		{ "OTHER_DEFINITION", "VIEW_DEFINITION" },
		{ "OTHER_DEFINITION", "SELECTION_DEFINITION" },
		{ "OTHER_DEFINITION", "CALCULATION_DEFINITION" },

		{ "AGGREGATE_DEFINITION", "'Aggregate'", "IDENTIFIER", "':'", "AGGREGATE_CLAUSE" },
		{ "AGGREGATE_DEFINITION", "'Aggregate'", "IDENTIFIER", "':'", "AGGREGATE_CLAUSE", "DEPENDENCY_CLAUSE" },
		{ "AGGREGATE_CLAUSE", "OPERATOR", "AGGREGATE_OPERATOR", "DIMENSION_COLLECTION" },
		{ "AGGREGATE_CLAUSE", "OPERATOR", "AGGREGATE_OPERATOR", "DIMENSION_COLLECTION", "RESTRICTION_CLAUSE" },
		{ "AGGREGATE_OPERATOR", "'all-children-of'" },
		{ "AGGREGATE_OPERATOR", "'all-parts-of'" },
		{ "RESTRICTION_CLAUSE", "'for'", "DIMENSION_COLLECTION" },
		{ "RESTRICTION_CLAUSE", "RESTRICTION_CLAUSE", "'for'", "DIMENSION_COLLECTION" },
		{ "OPERATOR", "'sum'" },
		{ "DEPENDENCY_CLAUSE", "'depends-on'", "IDENTIFIER_LIST" },

		{ "DIMENSION_COLLECTION", "IDENTIFIER" },
		{ "DIMENSION_COLLECTION", "IDENTIFIER", "BINARY_DIMENSION_OPERATOR", "DIMENSION_COLLECTION" },
		{ "DIMENSION_COLLECTION", "UNARY_DIMENSION_OPERATOR", "DIMENSION_COLLECTION" },
		{ "DIMENSION_COLLECTION", "DIMENSION_COLLECTION", "BINARY_DIMENSION_OPERATOR", "DIMENSION_COLLECTION" },
		
		{ "UNARY_DIMENSION_OPERATOR", "'any-child-of'" },
		{ "UNARY_DIMENSION_OPERATOR", "'any-part-of'" },
		
		{ "BINARY_DIMENSION_OPERATOR", "'without'" },
		{ "BINARY_DIMENSION_OPERATOR", "'union'" },
		{ "BINARY_DIMENSION_OPERATOR", "'+'" },
		{ "BINARY_DIMENSION_OPERATOR", "'-'" },
		{ "BINARY_DIMENSION_OPERATOR", "'intersection'" },

		{ "ATTRIBUTE_DECLARATION", "IDENTIFIER", "':'", "ATTRIBUTE_DEFINITION" },
		{ "ATTRIBUTE_DEFINITION", "IDENTIFIER_LIST", "'='", "IDENTIFIER_LIST" },
		{ "ATTRIBUTE_DEFINITION", "IDENTIFIER_LIST", "'='", "IDENTIFIER_LIST", "';'", "ATTRIBUTE_DEFINITION" },

		{ "MEASURE_DEFINITION", "'Measure'", "IDENTIFIER", "':'", "IDENTIFIER", "'in'", "IDENTIFIER" },

		{ "VALUE_DEFINITION", "'Value'", "`number`", "'for'", "IDENTIFIER_LIST", "'in'", "IDENTIFIER" },
		{ "VALUE_DEFINITION", "'Value'", "`number`", "'for'", "IDENTIFIER_LIST" },
		{ "VALUE_DEFINITION", "LOAD_VALUES" },

		{ "LOAD_VALUES", "'Values'", "'in'", "IDENTIFIER", "FILE" },
		{ "FILE", "'from-file'", "'('", "IDENTIFIER", "','", "FILETYPE", "')'" },
		{ "FILETYPE", "'CSV'" },

		{ "SYNONYME_DEFINITION", "'Synonyme'", "SYNONYME_LIST", "'='", "IDENTIFIER_LIST" },

		{ "SYNONYME_LIST", "SYNONYME" },
		{ "SYNONYME_LIST", "SYNONYME_LIST", "','", "SYNONYME" },
		{ "SYNONYME", "`stringdef`" },

		{ "IDENTIFIER_LIST", "IDENTIFIER" },
		{ "IDENTIFIER_LIST", "IDENTIFIER_LIST", "','", "IDENTIFIER" },
		{ "IDENTIFIER", "`stringdef`" },

		{ "VIEW_DEFINITION", "'Show'", "VIEW_TYPE", "IDENTIFIER", "':'", "VIEW_CLAUSE" },
		{ "VIEW_CLAUSE", "VIEW_CONSTRAINT" },
		{ "VIEW_CLAUSE", "VIEW_CLAUSE", "VIEW_CONSTRAINT" },
		{ "VIEW_CONSTRAINT", "DIMENSION_COLLECTION", "'as'", "IDENTIFIER_LIST" },
		{ "VIEW_TYPE", "'PIES'" },
		{ "VIEW_TYPE", "'QUADRANTS'" },
		{ "VIEW_TYPE", "'HEATMAPS'" },
		
		{ "SELECTION_DEFINITION", "'Selection'", "IDENTIFIER", "':'", "DIMENSION_COLLECTION" },

		{ Token.IGNORED, "`whitespaces`" },
		{ Token.IGNORED, "`cstylecomment`" },
		
		{ "CALCULATION_DEFINITION", "'Calculation'", "IDENTIFIER", "':'", "VARIABLE_DEFINITIONS",
		"'->'", "CALCULATION_EXPRESSION", "'as'", "DYNAMIC_DIMENSION_DEFINITIONS", "'in'", "IDENTIFIER" },
				
		{ "CALCULATION_EXPRESSION", "TERM", "'-'", "CALCULATION_EXPRESSION" },
		{ "CALCULATION_EXPRESSION", "TERM", "'+'", "CALCULATION_EXPRESSION" },
		{ "CALCULATION_EXPRESSION", "TERM" },
		{ "TERM", "FACTOR", "'*'", "TERM" },
		{ "TERM", "FACTOR", "'/'", "TERM" },
		{ "TERM", "FACTOR" },
		{ "FACTOR", "`identifier`" },
		{ "FACTOR", "`number`" },
		{ "FACTOR", "'('", "CALCULATION_EXPRESSION", "')'" },
		
		{ "VARIABLE_DEFINITIONS", "VARIABLE_DEFINITION" },
		{ "VARIABLE_DEFINITIONS", "VARIABLE_DEFINITION", "VARIABLE_DEFINITIONS" },

		{ "VARIABLE_DEFINITION", "'each'", "`identifier`", "':'", "UNBOUND_CONSTRAINTS" },
		{ "VARIABLE_DEFINITION", "`identifier`", "':'", "BOUND_CONSTRAINTS" },
		
		{ "UNBOUND_CONSTRAINTS", "UNBOUND_CONSTRAINT" },
		{ "UNBOUND_CONSTRAINTS", "UNBOUND_CONSTRAINT", "','", "UNBOUND_CONSTRAINTS" }, 
		{ "UNBOUND_CONSTRAINT" , "`identifier`", "'='", "'['", "DIMENSION_COLLECTION", "']'" },
		{ "UNBOUND_CONSTRAINT", "'['", "DIMENSION_COLLECTION", "']'" },
		
		{ "BOUND_CONSTRAINTS", "BOUND_CONSTRAINT" },
		{ "BOUND_CONSTRAINTS", "BOUND_CONSTRAINT", "','", "BOUND_CONSTRAINTS" },
		{ "BOUND_CONSTRAINT", "NAMED_CONSTRAINT" },
		{ "BOUND_CONSTRAINT", "SIMPLE_CONSTRAINT" },
		
		{ "NAMED_CONSTRAINT", "`identifier`", "'='", "SIMPLE_CONSTRAINT" },
		
		{ "SIMPLE_CONSTRAINT", "`identifier`" }, //exact
		{ "SIMPLE_CONSTRAINT", "`identifier`", "'!'" }, //parent
		{ "SIMPLE_CONSTRAINT", "`identifier`", "'-1'" }, //predecessor
		{ "SIMPLE_CONSTRAINT", "'['", "DIMENSION_COLLECTION", "']'" }, //collection
		

		{ "DYNAMIC_DIMENSION_DEFINITIONS", "DYNAMIC_DIMENSION_DEFINITION" },
		{ "DYNAMIC_DIMENSION_DEFINITIONS", "DYNAMIC_DIMENSION_DEFINITION", "','", "DYNAMIC_DIMENSION_DEFINITIONS" },
		{ "DYNAMIC_DIMENSION_DEFINITION", "IDENTIFIER" },
		{ "DYNAMIC_DIMENSION_DEFINITION", "IDENTIFIER", "'is-a'", "IDENTIFIER" },
		
	};
	
	public Object DYNAMIC_DIMENSION_DEFINITIONS (Object DYNAMIC_DIMENSION_DEFINITION) {
		final List<DynamicDimensionDefinition> ddds = Lists.newArrayList();
		ddds.add((DynamicDimensionDefinition) DYNAMIC_DIMENSION_DEFINITION);
		return ddds;
	}
	
	public Object DYNAMIC_DIMENSION_DEFINITIONS (Object DYNAMIC_DIMENSION_DEFINITION, Object x, Object DDDS) {
		final List<DynamicDimensionDefinition> ddds = (List<DynamicDimensionDefinition>) DDDS;
		ddds.add((DynamicDimensionDefinition) DYNAMIC_DIMENSION_DEFINITION);
		return ddds;
	}
	
	public Object DYNAMIC_DIMENSION_DEFINITION (Object IDENTIFIER) {
		return new DynamicDimensionDefinition((String) IDENTIFIER);
	}
	
	public Object DYNAMIC_DIMENSION_DEFINITION (Object IDENTIFIER, Object isa, Object PARENT) {
		final DynamicDimensionDefinition ddd = new DynamicDimensionDefinition((String) IDENTIFIER);
		ddd.setParent(context.getDimension((String) PARENT));
		return ddd;
	}
	
	public Object CALCULATION_EXPRESSION (Object LHS, Object OP, Object RHS) {
		if ("-".equals(OP)) {
			return new Substraction((Expression) LHS, (Expression) RHS);
		} else if ("+".equals(OP)) {
			return new Addition((Expression) LHS, (Expression) RHS);
		}
		
		else throw new IllegalStateException ("Operator " + OP + " undefined");
	}
	
	public Object CALCULATION_EXPRESSION (Object TERM) {
		return TERM;
	}
	
	public Object TERM (Object LHS, Object OP, Object RHS) {
		if ("*".equals(OP)) {
			return new Multiplication((Expression) LHS, (Expression) RHS);
		} else if ("/".equals(OP)) {
			return new Division((Expression) LHS, (Expression) RHS);
		}
		else throw new IllegalStateException ("Operator " + OP + " undefined");
	}
	
	public Object TERM (Object FACTOR) {
		return FACTOR;
	}
	
	public Object FACTOR (Object IDENTIFIER) {
		try {
			Double constant = Double.parseDouble((String) IDENTIFIER);
			return new Constant(constant);
		} catch (Exception ex) {} 
		return new VariableExpression((String) IDENTIFIER);
	}
	
	public Object FACTOR (Object x, Object EXPRESSION, Object y) {
		return EXPRESSION;
	}
	
	public Object CALCULATION_DEFINITION (Object x, Object NAME, Object y, Object VARIABLE_DEFINITIONS,
					Object z, Object EXPRESSION, Object v, Object DYNAMIC_DIMENSION_DEFINITIONS, Object w, Object MEASUREMENT) {
		Variables variables = (Variables) VARIABLE_DEFINITIONS;
		
		
		List<VariableDefinition> unboundVariableDefinitions = Lists.newArrayList();
		for (UnboundVariable uv: variables.unboundVariables) {
			final VariableDefinition uvDef = new VariableDefinition(uv.name, context);
			for (Pair<String, DimensionCollection> constraint: uv.constraints) {
				uvDef.addConstraint(new UnboundConstraint(constraint.first(), context, constraint.second()));
			}
			unboundVariableDefinitions.add(uvDef);
		}
		
		List<VariableDefinition> boundVariableDefinitions = Lists.newArrayList();
		for (BoundVariable bv: variables.boundVariables) {
			final VariableDefinition bvDef = new VariableDefinition(bv.name, context);
			
			for (BoundConstraint constraint: bv.constraints) {
				if (constraint.collection != null) {
					bvDef.addConstraint(new UnboundConstraint("__bub" + Math.random(), context, constraint.collection));
				} else {
					assert (constraint.namedConstraint != null);
					final NamedConstraint namedConstraint = constraint.namedConstraint;
					if (!namedConstraint.bound) {
						bvDef.addConstraint(new UnboundConstraint("__nbub" + Math.random(), context, namedConstraint.collection));

					} else {
						
						final String boundName = namedConstraint.boundIdentifier;
						Constraint unboundConstraint = null;
						for (VariableDefinition uvDef: unboundVariableDefinitions) {
							if (uvDef.hasConstraint(boundName)) {
								if (unboundConstraint != null) {
									throw new IllegalStateException("Variable " + boundName + " is ambigous");
								}
								unboundConstraint = uvDef.getConstraint(boundName);
							}
						}
						
						if (unboundConstraint == null) {
							throw new IllegalStateException("Variable " + boundName + " does not exist!");
						}
						
						bvDef.addConstraint(new jflowmap.model.ontology.calculation.BoundConstraint(namedConstraint.identifier,
								unboundConstraint, namedConstraint.transform));
					}
				}
			}
		}
		
		final ResultDefinition resultDef = new ResultDefinition(context, new Calculation().use((Expression) EXPRESSION));
		resultDef.use(unboundVariableDefinitions);
		resultDef.use(boundVariableDefinitions);
		
		for (DynamicDimensionDefinition ddd: (List<DynamicDimensionDefinition>) DYNAMIC_DIMENSION_DEFINITIONS) {
			resultDef.addDimension(ddd);
		}
		
		resultDef.setMeasurement((String) MEASUREMENT);
		CalculationDefinition calc = new CalculationDefinition(resultDef, context);
		for (VariableDefinition uvDef: unboundVariableDefinitions) {
			calc.addUnboundVariable(uvDef);
		}
		calc.calculate();
		
		return null;
	}
	
	private static class Variables {
		public List<BoundVariable> boundVariables = Lists.newArrayList();
		public List<UnboundVariable> unboundVariables = Lists.newArrayList();
	}
	
	public Object VARIABLE_DEFINITIONS (Object VARIABLE_DEFINITION, Object VARIABLE_DEFINITIONS) {
		Variables variables = (Variables) VARIABLE_DEFINITIONS;
		if (VARIABLE_DEFINITION instanceof UnboundVariable) {
			variables.unboundVariables.add((UnboundVariable) VARIABLE_DEFINITION);
		} else {
			variables.boundVariables.add((BoundVariable) VARIABLE_DEFINITION);
		}
		return variables;
	}
	
	public Object VARIABLE_DEFINITIONS (Object VARIABLE_DEFINITION) {
		Variables variables = new Variables();
		
		if (VARIABLE_DEFINITION instanceof UnboundVariable) {
			variables.unboundVariables.add((UnboundVariable) VARIABLE_DEFINITION);
		} else {
			variables.boundVariables.add((BoundVariable) VARIABLE_DEFINITION);
		}
		
		return variables;
	}
	

	private static class UnboundVariable {
		public List<Pair<String, DimensionCollection>> constraints;
		public String name;
	}
	
	public Object VARIABLE_DEFINITION (Object each, Object IDENTIFIER, Object x, Object UNBOUND_CONSTRAINTS) {
		UnboundVariable v = new UnboundVariable();
		assert (UNBOUND_CONSTRAINTS instanceof List);
		v.constraints = (List<Pair<String, DimensionCollection>>) UNBOUND_CONSTRAINTS;
		v.name = (String) IDENTIFIER;
		return v;
	}
	
	private static class BoundVariable {
		public List<BoundConstraint> constraints;
		public String name;
	}
	
	public Object VARIABLE_DEFINITION (Object IDENTIFIER, Object x, Object BOUND_CONSTRAINTS) { //bound
		BoundVariable v = new BoundVariable();
		v.constraints = (List<BoundConstraint>) BOUND_CONSTRAINTS;
		v.name = (String) IDENTIFIER;
		return v;
	}
	
	public Object UNBOUND_CONSTRAINTS (Object UNBOUND_CONSTRAINT, Object x, Object UNBOUND_CONSTRAINTS) {
		final List<Pair<String, DimensionCollection>> ret = (List<Pair<String, DimensionCollection>>) UNBOUND_CONSTRAINTS;
		assert (UNBOUND_CONSTRAINT instanceof Pair);
		ret.add((Pair<String, DimensionCollection>) UNBOUND_CONSTRAINT);
		return ret;
	}
	
	public Object UNBOUND_CONSTRAINTS (Object UNBOUND_CONSTRAINT) {
		final List<Pair<String, DimensionCollection>> ret = Lists.newArrayList();
		assert (UNBOUND_CONSTRAINT instanceof Pair);
		ret.add((Pair<String, DimensionCollection>) UNBOUND_CONSTRAINT);
		return ret;
	}
	

	public Object UNBOUND_CONSTRAINT (Object y, Object DIMENSION_COLLECTION, Object z) {
		return Pair.of("__u" + Math.random(), convertToDimensionCollection(DIMENSION_COLLECTION));
	}
	
	public Object UNBOUND_CONSTRAINT (Object IDENTIFIER, Object x, Object y, Object DIMENSION_COLLECTION, Object z) {
		return Pair.of((String) IDENTIFIER, convertToDimensionCollection(DIMENSION_COLLECTION));
	}
	
	private static class NamedConstraint {
		public String identifier;
		public String boundIdentifier;
		public DimensionCollection collection;
		public Function<Dimension, Dimension> transform;
		public boolean bound;
	}
	
	private static class BoundConstraint {
		public NamedConstraint namedConstraint;
		public DimensionCollection collection;
	}
	
	public Object BOUND_CONSTRAINTS (Object BOUND_CONSTRAINT, Object x, Object BOUND_CONSTRAINTS) {
		final List<BoundConstraint> ret = (List<BoundConstraint>) BOUND_CONSTRAINTS;
		ret.add((BoundConstraint)BOUND_CONSTRAINT);
		return ret;
	}
	
	public Object BOUND_CONSTRAINTS (Object BOUND_CONSTRAINT) {
		final List<BoundConstraint> ret = Lists.newArrayList();
		ret.add((BoundConstraint) BOUND_CONSTRAINT);
		return ret;
	}
	
	public Object BOUND_CONSTRAINT (Object CONSTRAINT) {
		BoundConstraint b = new BoundConstraint();
		
		if (CONSTRAINT instanceof NamedConstraint) {	
			b.namedConstraint = (NamedConstraint) CONSTRAINT;
			
		} else if (CONSTRAINT instanceof Pair) { //constraint = simple constraint
			b.namedConstraint = new NamedConstraint();
			b.namedConstraint.bound = true;
			b.namedConstraint.identifier = "_" + Math.random();
			b.namedConstraint.boundIdentifier = (String) ((Pair) CONSTRAINT).first();
			b.namedConstraint.transform = (Function<Dimension, Dimension>) ((Pair) CONSTRAINT).second();
			
		} else { //constraint = dimension collection
			b.collection = convertToDimensionCollection(CONSTRAINT);
		}
		
		return b;
	}
	
	public Object NAMED_CONSTRAINT (Object NAME, Object X, Object SIMPLE_CONSTRAINT) {
		NamedConstraint ret = new NamedConstraint();
		ret.identifier = (String) NAME;
		
		if (SIMPLE_CONSTRAINT instanceof Pair) {
			ret.bound = true;
			ret.boundIdentifier = (String) ((Pair) SIMPLE_CONSTRAINT).first();
			ret.transform = (Function<Dimension, Dimension>) ((Pair) SIMPLE_CONSTRAINT).second();

		} else {
			ret.bound = false;
			ret.collection = this.convertToDimensionCollection(SIMPLE_CONSTRAINT);
		}
		return ret;
	}
	
	public Object SIMPLE_CONSTRAINT (Object IDENTIFIER) { //exact
		return Pair.of((String) IDENTIFIER, jflowmap.model.ontology.calculation.BoundConstraint.SAME);
	}
	
	public Object SIMPLE_CONSTRAINT (Object IDENTIFIER, Object OP) { //parent
		final String operation = (String) OP;
		if ("!".equals(operation)) {
			return Pair.of((String) IDENTIFIER, jflowmap.model.ontology.calculation.BoundConstraint.SAME); //TODO => parent
		} else if ( "-1".equals(operation)) {
			return Pair.of((String) IDENTIFIER, jflowmap.model.ontology.calculation.BoundConstraint.PREDECESSOR);
		}
		
		throw new IllegalStateException("No constraining operation specified!");
	}
	
	public Object SIMPLE_CONSTRAINT (Object X, Object DIMENSION_COLLECTION, Object Y) { //collection
		return DIMENSION_COLLECTION;
	}
	
	
	
	
	

	private Map<String, String> synonymes = Maps.newHashMap();
	private final ViewConfig config;

	private ContextBuilder(ViewConfig config) {
		this.config = config;
	}
	
	public Object SELECTION_DEFINITION (Object x, Object IDENTIFIER, Object y, Object DIMENSION_COLLECTION) {
		context.defineSelection((String) IDENTIFIER).add(this.convertToDimensionCollection(DIMENSION_COLLECTION));
		return null;
	}
	
	@SuppressWarnings("unchecked")
	public Object INCLUDE_DIRECTIVE (Object x, Object IDENTIFIER_LIST) {
		List<String> files = (List<String>) IDENTIFIER_LIST;
		Collections.reverse(files);
		
		for (String filename: files) {
			this.includeFile(filename);
		}
		
		return null;
	}

	@SuppressWarnings("unchecked")
	public Object VIEW_DEFINITION (Object x, Object VIEW_TYPE, Object IDENTIFIER, Object y, Object VIEW_CLAUSE) {
		final Map<String, DimensionCollection> constraints = (Map<String, DimensionCollection>) VIEW_CLAUSE;

		final ViewDefinition viewDefinition = context.defineView((String) IDENTIFIER);
		viewDefinition.as((String) VIEW_TYPE);

		for (Entry<String, DimensionCollection> constraint: constraints.entrySet()) {
			viewDefinition.with(constraint.getKey(), constraint.getValue());
		}

		return null;
	}

	@SuppressWarnings("unchecked")
	public Object VIEW_CLAUSE (Object VIEW_CLAUSE, Object VIEW_CONSTRAINT) {
		final Map<String, DimensionCollection> constraints = (Map<String, DimensionCollection>) VIEW_CONSTRAINT;
		final Map<String, DimensionCollection> clause = (Map<String, DimensionCollection>) VIEW_CLAUSE;
		
		clause.putAll(constraints);
		
		return clause;
	}

	public Object VIEW_CLAUSE (Object VIEW_CONSTRAINT) {
		return VIEW_CONSTRAINT;
	}

	@SuppressWarnings("unchecked")
	public Object VIEW_CONSTRAINT (Object DIMENSION_COLLECTION, Object x, Object IDENTIFIER_LIST) {
		final Map<String, DimensionCollection> constraints = Maps.newHashMap();
		final DimensionCollection collection = convertToDimensionCollection(DIMENSION_COLLECTION);
		
		for (String identifier: (List<String>) IDENTIFIER_LIST) {
			constraints.put(identifier, collection);
		}
		return constraints;
	}

	public Object SYNONYME_LIST (Object SYNONYME_LIST, Object x, Object SYNONYME) {
		@SuppressWarnings("unchecked")
		final List<String> result = (List<String>) SYNONYME_LIST;
		result.add((String)SYNONYME);
		return result;
	}

	public Object SYNONYME_LIST (Object SYNONYME) {
		final List<String> result = Lists.newLinkedList();
		result.add((String)SYNONYME);
		return result;
	}

	public Object SYNONYME (Object ID) {
		final String identifier = (String) ID;
		return identifier.substring(1, identifier.length()-1);
	}

	public Object SYNONYME_DEFINITION (Object x, Object SYNONYMES, Object y, Object IDENTIFIERS) {
		@SuppressWarnings("unchecked")
		final Collection<String> synonymes = (Collection<String>) SYNONYMES;
		@SuppressWarnings("unchecked")
		final Collection<String> identifiers = (Collection<String>) IDENTIFIERS;

		for (Iterator<String> synonyme = synonymes.iterator(), identifier = identifiers.iterator();
				synonyme.hasNext() && identifier.hasNext();) {
			final String s = synonyme.next();

			String i = resolveSynonyme(identifier.next());
			System.out.println(s + " = " + i);

			this.synonymes.put(s, i);
		}
		return null;
	}

	private String resolveSynonyme(String name) {
		if (synonymes.containsKey(name))
			return synonymes.get(name);
		else return name;
	}

	public Object LOAD_VALUES (Object x, Object y, Object MEASURE, Object FILE) {
		@SuppressWarnings("unchecked")
		final Map<String, String> properties = (Map<String, String>) FILE;

		Collection<Value> values = null;
		try {
			values = ValueReaders.valueOf(properties.get("__filetype")).load(properties, context);

			for (Value value: values) {
				value.in(context.getMeasurement((String)MEASURE));
			}

		} catch (IOException e) {
			System.out.println("Error reading " + properties.get("__filename"));
			e.printStackTrace();
		}

		return null;
	}

	public Object FILE (Object x, Object y, Object FILENAME, Object z, Object FILETYPE, Object u) {
		final String filetype = (String) FILETYPE;
		final String filename = (String) FILENAME;

		Map<String, String> properties = Maps.newHashMap();

		System.out.println("Reading values from " + filename);

		properties.putAll(synonymes);
		properties.put("__filename", config.relativeFileLocation(filename));
		properties.put("__filetype", filetype);
		properties.put("__encoding", System.getProperty("file.encoding"));
		properties.put("__separator", ";");

		return properties;
	}

	public Object IDENTIFIER (Object ID) {
		final String identifier = (String) ID;
		return resolveSynonyme(identifier.substring(1, identifier.length()-1));
	}

	public Object MEASURE_DEFINITION (Object x, Object NAME, Object y, Object TYPE, Object z, Object UNIT) {
		context.defineMeasurement((String) NAME).type((String) TYPE).in((String) UNIT);
		return null;
	}

	public Object VALUE_DEFINITION (Object x, Object NUMBER, Object y, Object DIMENSIONS, Object z, Object MEASURE) {
		final Value v = (Value) VALUE_DEFINITION(x, NUMBER, y, DIMENSIONS);

		Measurement m = context.getMeasurement((String)MEASURE);
		v.in(m);

		return v;
	}

	public Object VALUE_DEFINITION (Object x, Object NUMBER, Object y, Object DIMENSIONS) {
		Double value = Double.valueOf((String) NUMBER);
		@SuppressWarnings("unchecked")
		Collection<String> dimensions = (Collection<String>) DIMENSIONS;

		final Value v = context.create(value);	
		for (String d: dimensions) {
			v.as(d);
		}
		return v;
	}

	public Object OPERATOR (Object OP) {
		return ((String) OP).toUpperCase();
	}

	public Object AGGREGATE_DEFINITION (Object x, Object IDENTIFIER, Object y,
			Object AGGREGATE_CLAUSE, Object DEPENDENCY_CLAUSE) {
		AGGREGATE_DEFINITION(null, IDENTIFIER, null, AGGREGATE_CLAUSE);
		final Aggregation agg = context.getAggregation((String) IDENTIFIER);

		@SuppressWarnings("unchecked")
		Collection<String> dependencies = (Collection<String>) DEPENDENCY_CLAUSE;

		for (String dependency: dependencies) {
			if (!context.aggregations.keySet().contains(dependency)) {
				throw new IllegalStateException("dependency " +  dependency +  " not satisfied: it does not exist!"
						+ " Define it first!");
			}
			agg.needs(context.getAggregation(dependency));
		}

		return null;
	}

	public Object AGGREGATE_DEFINITION (Object x, Object IDENTIFIER, Object y, Object AGGREGATE_CLAUSE) {
		context.aggregations.put((String) IDENTIFIER, (Aggregation) AGGREGATE_CLAUSE);
		return null;
	}

	public Object DEPENDENCY_CLAUSE (Object x, Object IDENTIFIER_LIST) {
		return IDENTIFIER_LIST;
	}

	public Object IDENTIFIER_LIST (Object IDENTIFIER_LIST, Object x, Object IDENTIFIER) {
		@SuppressWarnings("unchecked")
		List<String> list = (List<String>) IDENTIFIER_LIST;
		list.add((String) IDENTIFIER);
		return list;
	}

	public Object IDENTIFIER_LIST (Object IDENTIFIER) {
		List<String> list = Lists.newLinkedList();
		list.add((String) IDENTIFIER);
		return list;
	}

	public Object ATTRIBUTE_DEFINITION (Object KEYS, Object y, Object VALUES, Object x, Object ATTRIBUTE_DEFINITION) {
		@SuppressWarnings("unchecked")
		final Map<String, String> attributes = (Map<String, String>) ATTRIBUTE_DEFINITION;
		@SuppressWarnings("unchecked")
		final Collection<String> keys = (Collection<String>) KEYS;
		@SuppressWarnings("unchecked")
		final Collection<String> values = (Collection<String>) VALUES;

		for (Iterator<String> key = keys.iterator(), value = values.iterator(); key.hasNext() && value.hasNext();) {
			attributes.put(key.next(), value.next());
		}
		return attributes;
	}

	public Object ATTRIBUTE_DEFINITION (Object KEYS, Object y, Object VALUES) {
		final Map<String, String> attributes = Maps.newHashMap();
		@SuppressWarnings("unchecked")
		final Collection<String> keys = (Collection<String>) KEYS;
		@SuppressWarnings("unchecked")
		final Collection<String> values = (Collection<String>) VALUES;
		for (Iterator<String> key = keys.iterator(), value = values.iterator(); key.hasNext() && value.hasNext();) {
			attributes.put(key.next(), value.next());
		}

		return attributes;
	}

	public Object ATTRIBUTE_DECLARATION (Object IDENTIFIER, Object x, Object ATTRIBUTE_DEFINITION) {
		@SuppressWarnings("unchecked")
		final Map<String, String> attributes = (Map<String, String>) ATTRIBUTE_DEFINITION;
		final Dimension dimension = context.getDimension((String)IDENTIFIER);

		for (Entry<String, String> pair: attributes.entrySet()) {
			System.out.println(dimension.getName() + ": " + pair.getKey() + " = " + pair.getValue());
			dimension.set(pair.getKey(), pair.getValue());
		}

		return null;
	}

	private Context context;

	private DimensionCollection convertToDimensionCollection (Object DIMENSION_COLLECTION) {
		final DimensionCollection dimensionCollection = new DimensionCollection(context);	
		@SuppressWarnings("unchecked")
		final List<Object> coll = (List<Object>) DIMENSION_COLLECTION;
		Collections.reverse(coll);

		for (Object obj: coll) {
			if (obj.equals("any-child-of")) {
				dimensionCollection.anyChildOf();

			} else if (obj.equals("any-part-of")) {
				dimensionCollection.anyPartOf();

			} else if (obj.equals("without") || obj.equals("-")) {
				dimensionCollection.without();

			} else if (obj.equals("union") || obj.equals("+")) {
				dimensionCollection.union();

			} else if (obj.equals("intersection")) {
				dimensionCollection.intersection();

			} else if (obj instanceof Dimension) {
				dimensionCollection.dimension(((Dimension)obj).getName());
			}
		}
		return dimensionCollection;
	}


	public Object RESTRICTION_CLAUSE (Object RESTRICTION_CLAUSE, Object forEvery, Object DIMENSION_COLLECTION) {
		final Aggregation aggregation = (Aggregation) RESTRICTION_CLAUSE;
		final Set<Dimension> dimensions = convertToDimensionCollection(DIMENSION_COLLECTION).get();
		System.out.println(" and for every " + dimensions);
		aggregation.forEvery(dimensions);
		return aggregation;
	}

	public Object RESTRICTION_CLAUSE (Object forEvery, Object DIMENSION_COLLECTION) {
		final Aggregation aggregation = new Aggregation(context);
		final Set<Dimension> dimensions = convertToDimensionCollection(DIMENSION_COLLECTION).get();
		System.out.println("for every " + dimensions);
		aggregation.forEvery(dimensions);
		return aggregation;
	}

	public Object DIMENSION_COLLECTION (Object IDENTIFIER) {
		final List<Object> dimensionCollectionOperations = new Vector<Object>();
		dimensionCollectionOperations.add(context.getDimension((String)IDENTIFIER));
		return dimensionCollectionOperations;
	}

	public Object DIMENSION_COLLECTION (Object UNARY_DIMENSION_OPERATOR, Object DIMENSION_COLLECTION) {
		@SuppressWarnings("unchecked")
		final List<Object> dimensionCollectionOperations = (List<Object>) DIMENSION_COLLECTION;
		dimensionCollectionOperations.add((String)UNARY_DIMENSION_OPERATOR);
		return dimensionCollectionOperations;
	}
	
	@SuppressWarnings("unchecked")
	public Object DIMENSION_COLLECTION (Object DIMENSION_COLLECTION_LHS, Object BINARY_DIMENSION_OPERATOR, 
			Object DIMENSION_COLLECTION_RHS) {
		
		final List<Object> dimensionCollectionOperations = (List<Object>) DIMENSION_COLLECTION_RHS;
		
		dimensionCollectionOperations.add((String) BINARY_DIMENSION_OPERATOR);
		
		if (DIMENSION_COLLECTION_LHS instanceof List) {
			dimensionCollectionOperations.addAll((List<Object>) DIMENSION_COLLECTION_LHS);
		} else {
			dimensionCollectionOperations.add(context.getDimension((String) DIMENSION_COLLECTION_LHS));
		}
		
		return dimensionCollectionOperations;
	}

	@SuppressWarnings("unchecked")
	public Set<Dimension> convertToDimensions(Object DIMENSION_LIST) {
		Set<Dimension> dimensions = Sets.newHashSet();
		for (String d: (Collection<String>) DIMENSION_LIST) {
			dimensions.add(context.getDimension(d));
		}
		return dimensions;
	}

	public Object AGGREGATE_CLAUSE (Object OPERATOR, Object AGGREGATE_OPERATOR,
			Object DIMENSION_COLLECTION) {
		final Aggregation aggregation = new Aggregation(context);
		final Set<Dimension> dimensions = convertToDimensionCollection(DIMENSION_COLLECTION).get();

		if (AGGREGATE_OPERATOR.equals("all-children-of")) {
			aggregation.aggregateAllValuesChildrenOf(dimensions);
		} else {
			aggregation.aggregateAllValuesPartsOf(dimensions);
		}

		System.out.println ("=> calculating " + (String) OPERATOR + " of " + dimensions);
		return aggregation.with(Aggregators.valueOf((String) OPERATOR));
	}

	public Object AGGREGATE_CLAUSE (Object OPERATOR, Object AGGREGATE_OPERATOR,
			Object DIMENSION_COLLECTION, Object RESTRICTION_CLAUSE) {

		final Aggregation aggregation = (Aggregation) RESTRICTION_CLAUSE;
		final String aggregateOperator = (String) AGGREGATE_OPERATOR;
		final Set<Dimension> dimensions = convertToDimensionCollection(DIMENSION_COLLECTION).get();

		if (aggregateOperator.equals("all-children-of")) {
			aggregation.aggregateAllValuesChildrenOf(dimensions);
		} else {
			aggregation.aggregateAllValuesPartsOf(dimensions);
		}

		System.out.println ("=> calculating " + (String) OPERATOR + " of " + dimensions);
		return aggregation.with(Aggregators.valueOf((String)OPERATOR));
	}


	public Object NAMESPACE (Object context, Object contextName) {
		System.out.println("Entering context " + (String)contextName);
		this.context = Context.get((String)contextName);
		return null;
	}

	public Object DIMENSION_LIST (Object DIMENSION_LIST, Object comma, Object IDENTIFIER) {
		@SuppressWarnings("unchecked")
		Collection<String> coll = (Collection<String>) DIMENSION_LIST;
		coll.add((String) IDENTIFIER);
		return DIMENSION_LIST;
	}

	public Object DIMENSION_LIST (Object IDENTIFIER) {
		final List<String> list = Lists.newLinkedList();
		list.add((String)IDENTIFIER);//Sets.newHashSet((String)IDENTIFIER);
		return list;
	}
	
	public Object DIMENSION_LIST (Object ordered, Object x, Object DIMENSION_LIST, Object y) {
		@SuppressWarnings("unchecked")
		Collection<String> coll = (Collection<String>) DIMENSION_LIST;
		Dimension last = null;
		for (String dimName: coll) {
			if (last != null) {
				last.preceding(dimName);
			}
			
			last = context.getDimension(dimName);
		}
		
		return DIMENSION_LIST;
	}

	public Object DIMENSION_DEFINITION (Object DIMENSION_LIST, Object rel, Object IDENTIFIER) {
		final String relation = (String) rel;
		@SuppressWarnings("unchecked")
		final Collection<String> dimensions = (Collection<String>) DIMENSION_LIST;

		if (relation.equals("is-a")) {
			for (String dimension: dimensions) {
				System.out.println(dimension + " is-a " + (String)IDENTIFIER);
				context.defineDimension(dimension).childOf((String)IDENTIFIER);
			}
		} else {
			for (String dimension: dimensions) {
				System.out.println(dimension + " part-of " + (String)IDENTIFIER);
				context.defineDimension(dimension).partOf((String)IDENTIFIER);
			}
		}
		return null;
	}

	public void includeFile (final String filename) {

		final Context oldContext = this.context;

		try {
			String input = readFileAsString(config.relativeFileLocation(filename.toLowerCase() + ".inc"));
			if (parse(input)) {
				System.out.println("Included " + filename.toLowerCase() + ".inc successfully");
			} else {
				System.out.println("Error including " + filename);
			}
		} catch (Exception ex) {
			System.out.println("Error while parsing " + filename);
			ex.printStackTrace();
		}

		this.context = oldContext;		
	}

	public boolean parse (final String input) throws Exception {
		Syntax syntax = new Syntax(rules);
		SyntaxSeparation separation = new SyntaxSeparation(syntax);
		LexerBuilder builder = new LexerBuilder(separation.getLexerSyntax(), separation.getIgnoredSymbols());
		Lexer lexer = builder.getLexer();
		lexer.setInput(input);

		ParserTables parserTables = new LALRParserTables(separation.getParserSyntax());
		Parser parser = new Parser(parserTables);

		return parser.parse(lexer, this);
	}

	public static Context readFromFile (ViewConfig config, String filename) throws Exception   {
		System.out.println("Loading context-definition(s) from " + filename);

		String input = readFileAsString(filename);

		Syntax syntax = new Syntax(rules);
		SyntaxSeparation separation = new SyntaxSeparation(syntax);
		LexerBuilder builder = new LexerBuilder(separation.getLexerSyntax(), separation.getIgnoredSymbols());
		Lexer lexer = builder.getLexer();
		lexer.setInput(input);

		ParserTables parserTables = new LALRParserTables(separation.getParserSyntax());
		Parser parser = new Parser(parserTables);

		ContextBuilder cb = new ContextBuilder(config);
		if (parser.parse(lexer, cb)) {
			System.out.println("Context(s) loaded successfully!");
		} else {
			System.out.println("Error in loading Context(s)!");
		}

		return cb.context;
	}

	private static String readFileAsString(String filePath)
			throws java.io.IOException{
		StringBuffer fileData = new StringBuffer(1000);
		BufferedReader reader = new BufferedReader(
				new FileReader(filePath));
		char[] buf = new char[1024];
		int numRead=0;
		while((numRead=reader.read(buf)) != -1){
			String readData = String.valueOf(buf, 0, numRead);
			fileData.append(readData);
			buf = new char[1024];
		}
		reader.close();
		return fileData.toString();
	}

	public static Context parseConfig(ViewConfig config) throws Exception {
		return readFromFile(config, config.relativeFileLocation(config.require(ViewConfig.PROP_DATA_ONTOLOGY_DEFINITION)));
	}

}
